#include "libmaple.h"
#include "util.h"
#include "flash.h"
#include "flash_stm32.h"

typedef struct {
	volatile uint32 ACR;
	volatile uint32 KEYR;
	volatile uint32 OPTKEYR;
	volatile uint32 SR;
	volatile uint32 CR;
	volatile uint32 AR;
	uint32	RESERVED0;
	volatile uint32 OBR;
	volatile uint32 WRPR;
} FLASH_Port;

#ifndef FLASH_BASE
#define FLASH_BASE		((FLASH_Port*)0x40022000)
#endif

#define FLASH_FLAG_BSY		((uint32)0x00000001)  /*!< FLASH Busy flag */
#define FLASH_FLAG_PGERR	((uint32)0x00000004)  /*!< FLASH Program error flag */
#define FLASH_FLAG_WRPRTERR	((uint32)0x00000010)  /*!< FLASH Write protected error flag */
#define FLASH_FLAG_EOP		((uint32)0x00000020)  /*!< FLASH End of Operation flag */
#define FLASH_FLAG_OPTERR	((uint32)0x00000001)  /*!< FLASH Option Byte error flag */

/* Flash Control Register bits */
#define CR_PG_SET			((uint32)0x00000001)
#define CR_PG_RESET			((uint32)0x00001FFE)
#define CR_PER_SET			((uint32)0x00000002)
#define CR_PER_RESET		((uint32)0x00001FFD)

#define CR_STRT_SET			((uint32)0x00000040)
#define CR_LOCK_SET			((uint32)0x00000080)

#define FLASH_KEY1			((uint32)0x45670123)
#define FLASH_KEY2			((uint32)0xCDEF89AB)

/* Delay definition */
#define FLASH_EraseTimeout		((uint32)0x00000FFF)
#define FLASH_ProgramTimeout	((uint32)0x0000001F)

/**
  * @brief  Returns the FLASH Status.
  * @param  None
  * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
  *   FLASH_ERROR_WRP or FLASH_COMPLETE
  */
FLASH_STATUS FLASH_GetStatus(void)
{
	uint32 sr = FLASH_BASE->SR;
	if (sr & FLASH_FLAG_BSY)
		return FLASH_BUSY;

	if (sr & FLASH_FLAG_PGERR)
		return FLASH_ERROR_PG;

	if (sr & FLASH_FLAG_WRPRTERR)
		return FLASH_ERROR_WRP;

	if (sr & FLASH_FLAG_OPTERR)
		return FLASH_ERROR_OPT;

	return FLASH_COMPLETE;
}

/**
  * @brief  Waits for a Flash operation to complete or a TIMEOUT to occur.
  * @param  Timeout: FLASH progamming Timeout
  * @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
  *   FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
  */
FLASH_STATUS FLASH_WaitForLastOperation(uint32 Timeout)
{ 
	FLASH_STATUS status;
	volatile uint32 i;

	/* Check for the Flash Status */
	status = FLASH_GetStatus();
	/* Wait for a Flash operation to complete or a TIMEOUT to occur */
	while ((status == FLASH_BUSY) && (Timeout != 0x00))
	{
		for(i = 0xFF; i != 0; i--) { }
		status = FLASH_GetStatus();
		Timeout--;
	}
	if (Timeout == 0)
		status = FLASH_TIMEOUT;

	return status;
}

/**
  * @brief  Erases a specified FLASH page.
  * @param  Page_Address: The page address to be erased.
  * @retval FLASH Status: The returned value can be: FLASH_BUSY, FLASH_ERROR_PG,
  *   FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT.
  */
FLASH_STATUS FLASH_ErasePage(uint32 Page_Address)
{
	FLASH_STATUS status = FLASH_BAD_ADDRESS;

	if(IS_FLASH_ADDRESS(Page_Address))
	{
		/* Wait for last operation to be completed */
		status = FLASH_WaitForLastOperation(FLASH_EraseTimeout);
  
		if(status == FLASH_COMPLETE)
		{
			/* if the previous operation is completed, proceed to erase the page */
			FLASH_BASE->CR |= CR_PER_SET;
			FLASH_BASE->AR = Page_Address;
			FLASH_BASE->CR |= CR_STRT_SET;

			/* Wait for last operation to be completed */
			status = FLASH_WaitForLastOperation(FLASH_EraseTimeout);
			if(status != FLASH_TIMEOUT)
			{
				/* if the erase operation is completed, disable the PER Bit */
				FLASH_BASE->CR &= CR_PER_RESET;
			}
			FLASH_BASE->SR = (FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
		}
	}
	return status;
}

/**
  * @brief  Programs a half word at a specified address.
  * @param  Address: specifies the address to be programmed.
  * @param  Data: specifies the data to be programmed.
  * @retval FLASH Status: The returned value can be: FLASH_ERROR_PG,
  *   FLASH_ERROR_WRP, FLASH_COMPLETE or FLASH_TIMEOUT. 
  */
FLASH_STATUS FLASH_ProgramHalfWord(uint32 Address, uint16 Data)
{
	FLASH_STATUS status = FLASH_BAD_ADDRESS;

	if (IS_FLASH_ADDRESS(Address))
	{
		/* Wait for last operation to be completed */
		status = FLASH_WaitForLastOperation(FLASH_ProgramTimeout);
		if(status == FLASH_COMPLETE)
		{
			/* if the previous operation is completed, proceed to program the new data */
			FLASH_BASE->CR |= CR_PG_SET;
			*(__io uint16*)Address = Data;
			/* Wait for last operation to be completed */
			status = FLASH_WaitForLastOperation(FLASH_ProgramTimeout);
			if(status != FLASH_TIMEOUT)
			{
				/* if the program operation is completed, disable the PG Bit */
				FLASH_BASE->CR &= CR_PG_RESET;
			}
			FLASH_BASE->SR = (FLASH_FLAG_EOP | FLASH_FLAG_PGERR | FLASH_FLAG_WRPRTERR);
		}
	}
	return status;
}

/**
  * @brief  Unlocks the FLASH Program Erase Controller.
  * @param  None
  * @retval None
  */
void FLASH_Unlock(void)
{
  /* Authorize the FPEC Access */
  FLASH_BASE->KEYR = FLASH_KEY1;
  FLASH_BASE->KEYR = FLASH_KEY2;
}

/**
  * @brief  Locks the FLASH Program Erase Controller.
  * @param  None
  * @retval None
  */
void FLASH_Lock(void)
{
	/* Set the Lock Bit to lock the FPEC and the FCR */
	FLASH_BASE->CR |= CR_LOCK_SET;
}

